﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.UI.WebControls.WebParts;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.Office.Server.Search.WebControls;

namespace DotNetMafia.Office.Server.Search.WildcardSearch
{
	/// <summary>
	/// Wildcard Search Core Results Web Part
	/// Author: Corey Roth
	/// http://www.codexplex.com/WildcardSearch
	/// http://www.dotnetmafia.com
	/// This class inherits from CoreResultsWebPart to provide the ability to
	/// perform a wildcard search.
	/// </summary>
	public class WildcardSearchCoreResultsWebPart : CoreResultsWebPart
	{
		/// <summary>
		/// UserControlPath
		/// </summary>
		[XmlElement("AlwaysUseWildcard")]
		[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("Always Use Wildcard"), WebDescription("If checked will always perform a wildcard search.")]
		[Category("Miscellaneous")]
		[WebPartStorage(Storage.Shared), Browsable(false), DefaultValue(true)]
		public bool AlwaysUseWildcard
		{
			get;
			set;
		}

		/// <summary>
		/// This method sets the properties on the hidden object which 
		/// </summary>
		protected override void SetPropertiesOnHiddenObject()
		{
			// call the base object so that the default values get set on this object
			base.SetPropertiesOnHiddenObject();

			try
			{
				// get the type of the current object
				Type coreResultsWebPartType = this.GetType();

				// get the private field containing the searchResultsHiddenObject
				FieldInfo searchResultsHiddenObjectField = coreResultsWebPartType.BaseType.GetField("srho", BindingFlags.NonPublic | BindingFlags.Instance);

				// make sure the field exists
				if (searchResultsHiddenObjectField != null)
				{
					// get the actual internal srho object attached to CoreResultsWebPart
					object searchResultsHiddenObject = searchResultsHiddenObjectField.GetValue(this);

					// get the type of the srho
					Type searchResultsHiddenObjecType = searchResultsHiddenObject.GetType();

					// get the keyword query property
					PropertyInfo keywordQueryProperty = searchResultsHiddenObjecType.GetProperty("KeywordQuery", BindingFlags.Instance | BindingFlags.Public);

					// read what the user searched for
					string keywordQuery = (string)keywordQueryProperty.GetValue(searchResultsHiddenObject, null);

					// use this method to get the keywords as a query string to append to the search
					// this doesn't seem to be working yet
					MethodInfo keywordQueryMethod = searchResultsHiddenObjecType.GetMethod("GetKeywordsAsQuery", BindingFlags.Instance | BindingFlags.NonPublic);
					string keywordsAsQuery = (string)keywordQueryMethod.Invoke(searchResultsHiddenObject, null);

					// set the keywordProperty to null so we can change it to a fullTextQuery
					keywordQueryProperty.SetValue(searchResultsHiddenObject, null, null);

					// get the fullTextQuery field
					PropertyInfo fullTextQueryProperty = searchResultsHiddenObjecType.GetProperty("FullTextQuery", BindingFlags.Instance | BindingFlags.Public);

					// create a new query and set it
					string fullTextQueryString = GetFullTextQuery(keywordQuery, keywordsAsQuery);
					fullTextQueryProperty.SetValue(searchResultsHiddenObject, fullTextQueryString, null);

					// this field needs to be set to true to use a full text query
					FieldInfo fullTextQuerySetField = searchResultsHiddenObjecType.GetField("_IsFullTextQuerySetFromForm", BindingFlags.NonPublic | BindingFlags.Instance);
					fullTextQuerySetField.SetValue(searchResultsHiddenObject, true);

					// tell the srho that it is not a keyword query any more
					FieldInfo isKeywordQueryField = searchResultsHiddenObjecType.GetField("m_bIsKeywordQuery", BindingFlags.NonPublic | BindingFlags.Instance);
					isKeywordQueryField.SetValue(searchResultsHiddenObject, false);
				}
			}
			catch (Exception e)
			{
				// need to decide on an exception handling technique still
				string errorMessage = e.ToString();
			}
		}

		/// <summary>
		/// Return a full text query given a keyword query string
		/// </summary>
		/// <param name="keywordQuery"></param>
		/// <returns></returns>
		protected virtual string GetFullTextQuery(string keywordQuery, string keywordsAsQuery)
		{
			// create a new stringBuilder to hold the query
			StringBuilder fullTextQuery = new StringBuilder();

			// add the select statement
			fullTextQuery.Append("SELECT ");

			// load the columns list from the SelectColumns string containing XML
			XDocument columnsDocument = XDocument.Load(new StringReader(SelectColumns));

			var columns = from column in columnsDocument.Root.Element("Columns").Elements("Column")
						  select column.Attribute("Name").Value;

			// iterate through the columns array and build a query string
			bool hasColumnsYet = false;
			foreach (var columnName in columns)
			{
				// see if a seperator needs to be added
				if (hasColumnsYet)
				{
					fullTextQuery.Append(", ");
				}

				// append the columnName
				fullTextQuery.Append(columnName);
				hasColumnsYet = true;
			}

			// append the from clause
			fullTextQuery.Append(" FROM Scope() WHERE ");

			if (!string.IsNullOrEmpty(keywordsAsQuery))
			{
				// append the keyword query
				fullTextQuery.Append(keywordsAsQuery);

				// will always need an and
				fullTextQuery.Append(" AND ");
			}


			// append an asterisk if alwaysUseWildcard is on
			if ((AlwaysUseWildcard) && (!keywordQuery.EndsWith("*")))
				keywordQuery += "*";
			
			// append the wildcard query
			fullTextQuery.AppendFormat("CONTAINS ('\"{0}\"')", keywordQuery);

			return fullTextQuery.ToString();
		}
	}
}
